package com.semlab.server.graph;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.semlab.server.resources.ExecutionResultHandler;
import com.semlab.server.resources.SLog;

@Component
public class Neo4jTemplate {

	@Autowired
	EmbeddedGraphDatabase graphDB;

	@Autowired
	ExecutionEngine executionEngine;

	Transaction transaction;

	public Neo4jTemplate() {
	}

	public void beginTransaction() {
		transaction = graphDB.beginTx();
	}

	public void finishTransaction() {
		transaction.success();
		transaction.finish();
	}

	public Index<Node> getIndex(String concepts) {
		Index<Node> index = graphDB.index().forNodes(concepts);
		return index;
	}

	public Node createNode() {
		return graphDB.createNode();
	}

	public Node getNode(long nodeId) {
		return graphDB.getNodeById(nodeId);
	}

	public final Relationship relate(Node from, Node to, RelationshipType type) {
		// check if rel exist
		Iterable<Relationship> iterable = from.getRelationships(type,
				Direction.OUTGOING);
		for (Relationship relationship : iterable) {
			if (relationship.getEndNode().equals(to)) {
				return relationship;
			}
		}
		return from.createRelationshipTo(to, type);
	}

	public final void delete(Node node) {
		node.delete();
	}

	public final void setNodeProperty(Node node, String key, Object value) {
		if (value == null)
			return;

		if (value instanceof Long) {
			Long l = (Long) value;
			if (l == 0L)
				return;
		}

		if (value instanceof Integer) {
			Integer i = (Integer) value;
			if (i == 0)
				return;
		}

		node.setProperty(key, value.toString());

	}

	public final void setRelationshipProperty(Relationship rel, String key,
			String value) {
		rel.setProperty(key, value);
	}

	public final ArrayList<Node> getNodesByRelationship(Node node,
			RelationshipType type, Direction dir, boolean destroy) {
		ArrayList<Node> nodes = new ArrayList<Node>();
		Iterable<Relationship> rels = node.getRelationships(type, dir);
		for (Relationship relationship : rels) {
			if (dir == Direction.OUTGOING)
				nodes.add(relationship.getEndNode());
			else
				nodes.add(relationship.getStartNode());
			if (destroy)
				relationship.delete();
		}
		return nodes;
	}

	public final void deleteSubgraph(Node node, RelationshipType... types) {
		Iterator<Relationship> iterator;
		if (types == null) {
			iterator = node.getRelationships(Direction.OUTGOING).iterator();
		} else {
			iterator = node.getRelationships(Direction.OUTGOING, types)
					.iterator();
		}
		if (iterator.hasNext()) {
			while (iterator.hasNext()) {
				Relationship rel = iterator.next();
				deleteSubgraph(rel.getEndNode());
				rel.delete();
			}
		} else {
			node.delete();
		}
	}

	/**
	 * @param query
	 * @param handler
	 *            Leave handler null for default result handling
	 */
	public final ExecutionResult executeQuery(String query) {
		SLog.debug(getClass(), "cypher", "executing query", query);
		return executionEngine.execute(query);
	}

	public final ExecutionResult executeQuery(String query, boolean verbose) {
		ExecutionResult result = executeQuery(query);
		if (verbose) {
			// write result
			for (Map<String, Object> row : result) {
				for (Entry<String, Object> column : row.entrySet()) {
					SLog.debug(getClass(), column.getKey(), column.getValue());
				}
			}
		}
		return result;
	}

}
